package gates;

import exceptions.noQubitException;
import register.Register;
import gates.FunctionGate;
import mathtools.Complex;
/**
 * Functional representation of a Cnot gate:
 * This implements Gate interface, so consult this for public methods.
 * The constructor for this gate is protected, so generating such an object is left
 * for the factory class <code>Factory</code>
 * @author Jan Zaucha, James Hancox
 *
 */
public class CnotFunction extends FunctionGate {
	
	/** Store the target qubit the gate acts on*/	
	private int target;
	/** Store the control qubit the gate acts on*/	
	private int ctrl;



	/**
	 * Protected constructor for a functional representation of a Cnot gate
	 * @param ctrl The Qubit in the Quantum register to act as the Control - decide if the target has to be changed.
	 * @param target The Qubit in the Quantum register to act as the Target - the Qubit that will change from 0 - 1.
	 */
	protected CnotFunction(int ctrl, int target){
		if(ctrl==target){
			throw new IllegalArgumentException("ERROR: Cnot cannot be applied with equal Control and Target bits.");
		}
		this.target = target; 
		this.ctrl = ctrl;
	}

	/**
	 * Returns the name of the gate regardless of representation.
	 */
	public String getName() {
		return "Cnot";
	}

	/**
	 * Applies this gate to the given register. See interface <code>Gate</code> for usage.
	 * @param r the register 
	 */
	public void apply(Register r) {
		if(r.getNumQbits()<2){
			throw new noQubitException("ERROR: Cnot can only be applied to a Quantum Register with => 2 Quantum Bits.");
		}

		if (target >= r.getNumQbits() || target < 0){
			throw new IllegalArgumentException("Target qubit not in register. Register size (qubits): " + r.getNumQbits() + " Target qubit: " + target);
		}

		if (ctrl >= r.getNumQbits() || ctrl < 0){
			throw new IllegalArgumentException("Control qubit not in register. Register size (qubits): " + r.getNumQbits() + " Control qubit: " + ctrl);
		}
		int numStates = r.getHeight();    // number of states in register
		// create array to store updated register
		Complex [] newRegister = new Complex [numStates]; 
		int this_Qstate;
		int this_Tstate;
		int numQubits = r.getNumQbits();
		// loop around whole register
		for(int i=0; i<numStates; i++){
			// now shift right so that we have leftmost qubit the control qubit
			this_Qstate = i >> (numQubits - (ctrl +1));
			this_Tstate = i >> (numQubits - (target +1));

			if(this_Qstate % 2 == 0){
				// if get here don't flip
				newRegister[i] = r.getElement(0, i);
			}
			else{
				// else get 1 so flip
				// we need to determine which state to flip with
				if(this_Tstate % 2 == 0){
					// if get here means that target for this state is 0 so flip to 1
					newRegister[i] = r.getElement(0, i + (int)Math.pow(2, numQubits - (target +1) ));
				
				}
				else{
					// else get 1 so flip to 0
					newRegister[i] = r.getElement(0, i - (int)Math.pow(2, numQubits - (target +1) ));
				}
			}
		}
		
		r.update(newRegister);
		r.normalise();
	}




}
